<!DOCTYPE html>
<title>getHTML behavior</title>
<meta name="timeout" content="long">
<link rel='author' href='mailto:masonf@chromium.org'>
<link rel='help' href='https://github.com/whatwg/html/issues/8867'>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src='../../html/resources/common.js'></script>

<body>

<script>
function testElementType(allowsShadowDom, elementType, runGetHTMLOnShadowRoot,
      lightDOMContent, declarativeShadowDom, mode, delegatesFocus,
      serializable, clonable, emptyShadowTree) {
  const t = test(t => {
    // Create and attach element
    let wrapper;
    if (runGetHTMLOnShadowRoot) {
      // This ensures we're testing both Element.getHTML() and ShadowRoot.getHTML().
      const host = document.createElement('div');
      t.add_cleanup(function() { host.remove(); });
      document.body.appendChild(host);
      wrapper = host.attachShadow({mode: 'open'});
    } else {
      wrapper = document.createElement('div');
      t.add_cleanup(function() { wrapper.remove(); });
      document.body.appendChild(wrapper);
    }

    let shadowRoot;
    let initDict = {mode: mode, delegatesFocus: delegatesFocus, clonable};
    let expectedSerializable = null;
    switch (serializable) {
      case undefined: expectedSerializable = false; break;
      case "true": initDict.serializable = expectedSerializable = true; break;
      case "false": initDict.serializable = expectedSerializable = false; break;
      default: throw new Error(`Invalid serializable ${serializable}`);
    }
    const delegatesAttr = delegatesFocus ? ' shadowrootdelegatesfocus=""' : '';
    const serializableAttr = expectedSerializable ? ' shadowrootserializable=""' : '';
    const clonableAttr = clonable ? ' shadowrootclonable=""' : '';

    if (allowsShadowDom && declarativeShadowDom) {
      const html = `<${elementType}>${lightDOMContent}<template ` +
          `shadowrootmode=${mode}${delegatesAttr}${serializableAttr}` +
          `${clonableAttr}>`;
      wrapper.setHTMLUnsafe(html);
      // Get hold of the declarative shadow root in a way that works when its mode is "closed"
      shadowRoot = wrapper.firstElementChild.attachShadow(initDict);
    } else {
      // Imperative shadow dom
      const element = document.createElement(elementType);
      wrapper.appendChild(element);
      const temp = document.createElement('div');
      temp.innerHTML = lightDOMContent;
      element.append(...temp.childNodes);
      if (allowsShadowDom) {
        shadowRoot = element.attachShadow(initDict);
      }
    }
    assert_true(!allowsShadowDom || !!shadowRoot);

    if (allowsShadowDom) {
      const correctShadowHtml = `<template shadowrootmode="${mode}"` +
          `${delegatesAttr}${serializableAttr}${clonableAttr}>` +
          (emptyShadowTree ? `` : `<slot></slot>`) + `</template>`;
      const correctHtml = `<${elementType}>${correctShadowHtml}` +
          `${lightDOMContent}</${elementType}>`;
      assert_equals(shadowRoot.mode,mode);
      assert_equals(shadowRoot.delegatesFocus,delegatesFocus);
      assert_equals(shadowRoot.serializable,expectedSerializable);
      assert_equals(shadowRoot.clonable,clonable);
      if (!emptyShadowTree) {
        shadowRoot.appendChild(document.createElement('slot'));
      }
      const emptyElement = `<${elementType}>${lightDOMContent}</${elementType}>`;
      if (expectedSerializable) {
        assert_equals(wrapper.getHTML({serializableShadowRoots: true}),
            correctHtml);
        assert_equals(wrapper.firstElementChild.getHTML({
            serializableShadowRoots: true}),
            `${correctShadowHtml}${lightDOMContent}`);
      } else {
        assert_equals(wrapper.getHTML({serializableShadowRoots: true}), emptyElement);
      }
      // If we provide the shadow root, serialize it, regardless of serializableShadowRoots.
      assert_equals(wrapper.getHTML({serializableShadowRoots: true, shadowRoots:
          [shadowRoot]}),correctHtml);
      assert_equals(wrapper.getHTML({serializableShadowRoots: false, shadowRoots:
          [shadowRoot]}),correctHtml);
      assert_equals(wrapper.getHTML({shadowRoots: [shadowRoot]}),correctHtml);
    } else {
      // For non-shadow hosts, getHTML() should also match .innerHTML
      assert_equals(wrapper.getHTML({serializableShadowRoots: true}),wrapper.innerHTML);
    }

    // Either way, make sure getHTML({serializableShadowRoots: false}) matches .innerHTML
    assert_equals(wrapper.getHTML({serializableShadowRoots: false}),wrapper.innerHTML,
        'getHTML() with serializableShadowRoots false should return the same as .innerHTML');
    // ...and that the default for serializableShadowRoots is false.
    assert_equals(wrapper.getHTML(),wrapper.innerHTML,
        'The default for serializableShadowRoots should be false');

  }, `${runGetHTMLOnShadowRoot ? 'ShadowRoot' : 'Element'}.getHTML() on ` +
      `<${elementType}>${lightDOMContent}${allowsShadowDom ?
      `, with ${declarativeShadowDom ? 'declarative' : 'imperative'} shadow, ` +
      `mode=${mode}, delegatesFocus=${delegatesFocus}, ` +
      `serializable=${serializable}, clonable=${clonable},` : ''}` +
      ` with${emptyShadowTree ? `out` : ``} shadow tree contents.`);
}

function runAllTests() {
  const allElements = [...HTML5_ELEMENTS, 'htmlunknown'];
  const safelisted = HTML5_SHADOW_ALLOWED_ELEMENTS.filter(el => el != 'body');
  for (const elementName of allElements) {
    const allowsShadowDom = safelisted.includes(elementName);
    for (const runGetHTMLOnShadowRoot of [false, true]) {
      for (const lightDOMContent of ['','<span>light</span>']) {
        if (allowsShadowDom) {
          for (const declarativeShadowDom of [false, true]) {
            for (const delegatesFocus of [false, true]) {
              for (const clonable of [false, true]) {
                for (const mode of ['open', 'closed']) {
                  for (const serializable of [undefined, 'false', 'true']) {
                    for (const emptyShadowTree of [false, true]) {
                      testElementType(true, elementName, runGetHTMLOnShadowRoot,
                          lightDOMContent, declarativeShadowDom, mode,
                          delegatesFocus, serializable, clonable,
                          emptyShadowTree);
                    }
                  }
                }
              }
            }
          }
        } else {
          testElementType(false, elementName, runGetHTMLOnShadowRoot,
              lightDOMContent);
        }
      }
    }
  }
}

runAllTests();

</script>
